/*
	摄像头采集模块
*/
#include "camera.hpp"

#define CAMERA_BUFFER_CNT 32     //申请的缓冲区个数

int imx_fd;
int ret;
unsigned char *imx_mpaddr[CAMERA_BUFFER_CNT];   //有缓冲区，设置一个指针数组，存放映射到用户区的数组首地址
int imx_buf_length[CAMERA_BUFFER_CNT];
struct v4l2_buffer imx_mapbuffer;

void camera_init(u_int32_t width,u_int32_t height)
{
    //1打开设备
    imx_fd = open("/dev/video1", O_RDWR);
    if (imx_fd < 0)
    {
        perror("打开设备失败");
    }
    else
        printf("打开设备成功\n");

    //2获取摄像头支持的格式

    //3设置摄像头格式
    struct v4l2_format v4l2_fmt_set;
    v4l2_fmt_set.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    v4l2_fmt_set.fmt.pix.width = width;
    v4l2_fmt_set.fmt.pix.height = height;
    v4l2_fmt_set.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
    v4l2_fmt_set.fmt.pix.sizeimage = width * height;
    ret = ioctl(imx_fd,VIDIOC_S_FMT,&v4l2_fmt_set);
    if(ret < 0)
    {
        perror("设置失败");
    }

    //4申请缓冲区队列,内核空间
    struct v4l2_requestbuffers req_buf;
    memset(&req_buf,0,sizeof(req_buf));
    req_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req_buf.count = CAMERA_BUFFER_CNT;      //申请缓冲区
    req_buf.memory = V4L2_MEMORY_MMAP;
    ret = ioctl(imx_fd,VIDIOC_REQBUFS,&req_buf);
    if(ret < 0)
    {
        perror("申请队列空间失败");
    }
    else
        printf("申请队列成功\n");
    printf("实际申请到的缓冲区数量：%d\n",req_buf.count);//查看实际申请到的缓冲区数量
    //5内存映射
    
    //初始化type.index
    imx_mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    imx_mapbuffer.memory = V4L2_MEMORY_MMAP;
    for (int i = 0; i < req_buf.count; i++)
    {
        imx_mapbuffer.index = i;
        imx_mapbuffer.m.offset = 0;
        ret = ioctl(imx_fd,VIDIOC_QUERYBUF,&imx_mapbuffer);//申请内核空间长度
        if(ret < 0)
        {
            perror("申请内核空间失败");
        }
        
        printf("申请到的实际长度:%d\n",imx_mapbuffer.length);
        
        //进行映射
        imx_buf_length[i] = imx_mapbuffer.length;//存储长度用于后续释放映射，否则长度不一样会导致失败
        //按照申请到的实际长度进行映射
        imx_mpaddr[i] = (unsigned char *)mmap(NULL , imx_mapbuffer.length , PROT_READ | PROT_WRITE , MAP_SHARED , imx_fd , imx_mapbuffer.m.offset);
        if(imx_mpaddr[i] == MAP_FAILED)
        {
            perror("映射失败");
        }
        else
            printf("映射成功\n");

        ret = ioctl(imx_fd,VIDIOC_QBUF,&imx_mapbuffer);
        if(ret < 0)
            perror("查询后入队失败");
        else
            printf("查询后入队成功\n");
        //释放内存
    }
}

void camera_streadmon(void)
{
    //6开始采集
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(imx_fd,VIDIOC_STREAMON,&type);
    if(ret < 0)
        perror("开启采集失败");
    else
        printf("开始采集成功\n");
}

unsigned char * camera_capture_one(int *byte_used)
{
    //从队列中读取一帧数据
    struct v4l2_buffer read = {0};
    read.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    read.memory = V4L2_MEMORY_MMAP;
    read.length = 1;//平面长度
    ret = ioctl(imx_fd,VIDIOC_DQBUF,&read);
    if(ret < 0)
        perror("读取数据失败");
    /*else
        printf("读取数据成功\n");*/

    /*FILE *file = fopen("my.jpeg","w+");
    size_t plane_size = read.length;//往文件中写入一帧数据，即保存为图片
    fwrite(imx_mpaddr[read.index],1,plane_size,file);//读取会告诉占用了哪个缓冲区，从而寻找对应地址，写入数量是1,大小是是平面大小
    fclose(file);*/

    //通知内核使用完成，开始入队，只需要入队使用过到缓冲区
    struct v4l2_buffer qbuf = {0};
    qbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    qbuf.memory = V4L2_MEMORY_MMAP;
    qbuf.index = read.index;
    qbuf.length = 1;

    if(ioctl(imx_fd,VIDIOC_QBUF,&qbuf) < 0)
    {
        perror("qbuf failure");
    }
    /*else
        printf("入队成功\n");*/

    *byte_used = read.bytesused;

    return imx_mpaddr[read.index];
}

void camera_streamoff(void)
{
    //停止采集
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(imx_fd,VIDIOC_STREAMOFF,&type);
    if(ret < 0)
        perror("停止采集失败");
    else
        printf("停止采集成功\n");
    
}

void camera_close(void)
{
    //7释放映射
    for (int i = 0; i < CAMERA_BUFFER_CNT; i++)
    {
        if(imx_mpaddr[i] != MAP_FAILED)
        ret = munmap(imx_mpaddr[i],imx_buf_length[i]);
        if(ret < 0)
            perror("释放内存映射失败");
    }
    //9关闭设备
    close(imx_fd);
}

